// global object
var CodeHighlighter = {};

// common config
CodeHighlighter.config = {
	'enableDebug': false
};

// namespaces for language
CodeHighlighter.java = {};
CodeHighlighter.xml = {};
CodeHighlighter.javascript = {};
CodeHighlighter.css = {};

// java language
CodeHighlighter.java = {
	'keywords': [
		'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 
		'continue', 'default', 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float', 'for', 
		'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'package', 
		'private', 'protected', 'public', 'return', 'strictfp', 'short', 'static', 'super', 'switch', 'synchronized', 
		'this', 'throw', 'throws', 'transient', 'try', 'void', 'volatile', 'while', 'true', 'false', 'null'
	],
	'type':	{
		'unknown': -1,
		'blockComment': 0,
		'lineComment': 1,
		'literal': 2,
		'symbol': 3,
		'space': 4,
		'legal': 5,
		'keywords': 6,
		'annotation': 7
	},
	'classNames': {
		0: 'comment',
		1: 'comment',
		2: 'string',
		6: 'keywords',
		7: 'annotation'
	}
};

// main function
CodeHighlighter.highlight = function(text, type) {
	if (type == 'java') {
		return this.java.highligh(text);
	}
};

// html encoding
CodeHighlighter.html = function(s) {
	s = s.replace(/&/g, '&amp;');
	s = s.replace(/ /g, '&nbsp;');
	s = s.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
	s = s.replace(/>/g, '&gt;');
	s = s.replace(/</g, '&lt;');
	s = s.replace(/\"/g, '&quot;');
	s = s.replace(/\r\n/g, '\n');
	s = s.replace(/\n/g, '<br />\n');
	return s;
};
 
CodeHighlighter.java.isSymbol = function(seg, c, n) {
	var reg = new RegExp('^[\\(\\)\\[\\]\\{\\}\\.\\;\\+\\-\\*\\/\\%\\&\\|\\?\\:\\>\\<\\=\\!\\,\\^]$');
	if (c == '\/' && n == '\/') {
		return false;
	}
	if (c == '\/' && n == '*') {
		return false;
	}
	return reg.test(c);
};

CodeHighlighter.java.isSpace = function(seg, c, n) {
	var reg = new RegExp('^[ \r\n\t]$');
	return reg.test(c);
};
CodeHighlighter.java.isLegal = function(seg, c, n) {
	var reg = new RegExp('^[a-zA-Z0-9_\$@]$');
	return reg.test(c);
};

CodeHighlighter.java.isLiteral = function(seg, c, n) {
	var first = seg.charAt(0);
	var last = seg.charAt(seg.length - 1);
	var last2 = seg.charAt(seg.length - 2);
	if (seg == '' && (c == '\"' || c == '\'')) {
		return true;
	} else {
		if (seg.length > 1 && last2 != '\\' && (last == '\"' || last == '\'')) {
			return false;
		}
		if (first == '\"' || first == '\'') {
			return true;
		}
		if ((c == '\"' || c == '\'')) {
			return true;
		}
	}
	return false;
};

CodeHighlighter.java.isLineComment = function(seg, c, n) {
	var c1 = seg.charAt(0);
	var c2 = seg.charAt(1);
	if (c1 == '\"' || c1 == '\'') {
		return false;
	}
	if (seg.length == 1 && c1 == '\/' && n != '*') {
		return true;
	}
	if (c == '\/' && n == '\/') {
		return true;
	}
	if (c1 == '\/' && c2 == '\/' && c != '\r' && c != '\n') {
		return true;
	}
	return false;
};

CodeHighlighter.java.isBlockComment = function(seg, c, n) {
	var c1 = seg.charAt(0);
	var c2 = seg.charAt(1);
	var c3 = seg.charAt(seg.length - 2);
	var c4 = seg.charAt(seg.length - 1);
	if (c1 == '\"' || c1 == '\'') {
		return false;
	}
	if (c == '\/' && n == '*') {
		return true;
	}
	if (c == '*' && c1 == '\/') {
		return true;
	}
	if (seg.length >= 2 && c1 == '\/' && c2 == '*') {
		if (c3 == '*' && c4 == '\/') {
			return false;
		}
		return true;
	}
	return false;
};

CodeHighlighter.java.isKeywords = function(w) {
	var i, keywords = this.keywords;
	for (i = 0; i < keywords.length; i++) {
		if (w == keywords[i]) {
			return true;
		}
	}
	return false;
};

CodeHighlighter.java.isAnnotation = function(w) {
	var reg = new RegExp('^@[a-zA-Z0-9_\$]+$');
	return reg.test(w);
};

CodeHighlighter.java.highligh = function(text) {
	var i, c, n, seg = '', isNew = true, type, lastType = -1, arr = [], node, html = '';
	for (i = 0; i < text.length; i++ ) {
		c = text.charAt(i);
		n = text.charAt(i + 1);
		if (isNew) {
			lastType = this.getType(seg, c, n);
			seg += c;
			isNew = false;
		} else if (i == text.length - 1) {
			type = this.getType(seg, c, n);
			if (type == lastType) {
				seg += c;
			} else {
				seg = c;
			}
			if (this.isKeywords(seg, c, n)) {
				node = {'type': this.type.keywords, 'text': seg};
			} else if (this.isAnnotation(seg, c, n)) {
				node = {'type': this.type.annotation, 'text': seg};
			} else {
				node = {'type': lastType, 'text': seg};
			}
			arr.push(node);
		} else {
			type = this.getType(seg, c, n);
			if (type == lastType) {
				seg += c;
			} else {
				if (this.isKeywords(seg, c, n)) {
					node = {'type': this.type.keywords, 'text': seg};
				} else if (this.isAnnotation(seg, c, n)) {
					node = {'type': this.type.annotation, 'text': seg};
				} else {
					node = {'type': lastType, 'text': seg};
				}
				arr.push(node);
				lastType = this.getType(seg, c, n);
				seg = c;
			}
		}
	}
	for (i = 0; i < arr.length; i++) {
		if (CodeHighlighter.config.enableDebug) {
			console.log('type: ' + arr[i].type + ', text: ' + arr[i].text);
		}
		html += this.formatNodeHtml(arr[i]);
	}
	return html;
};

CodeHighlighter.java.getType = function(seg, c, n) {
	if (this.isBlockComment(seg, c, n)) {
		return this.type.blockComment;
	} else if (this.isLineComment(seg, c, n)) {
		return this.type.lineComment;
	} else if (this.isLiteral(seg, c, n)) {
		return this.type.literal;
	} else if (this.isSymbol(seg, c, n)) {
		return this.type.symbol;
	} else if (this.isSpace(seg, c, n)) {
		return this.type.space;
	} else if (this.isLegal(seg, c, n)) {
		return this.type.legal;
	} else {
		return this.type.unknown;
	}
};

CodeHighlighter.java.formatNodeHtml = function(node) {
	var type = node.type, text = node.text, result;
	if (this.classNames[type] !== undefined) {
		return '<span class=\"' + this.classNames[type] + '\">' + CodeHighlighter.html(node.text) + '</span>';
	}
	return CodeHighlighter.html(node.text);
};
